﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;


namespace DailyFx.UI
{
    public class WeakEventAction
    {
		/// <summary>
		///		
		/// </summary>
        readonly MethodInfo _method;
        
		/// <summary>
		///		
		/// </summary>
		readonly Type _delegateType;



        /// <summary>
        ///		C'tor 
        /// </summary>
        public WeakEventAction ( object target, MethodInfo method, Type paramType )
        {
            this.TargetObject = new WeakReference( target );

            this._method = method;
            this._delegateType = paramType == null
										? typeof( Action )
										: typeof( Action<>).MakeGenericType( paramType);
        }

		/// <summary>
		///		Gets the target to invoke the action .
		/// </summary>
        public WeakReference TargetObject 
		{ 
			get; 
			private set; 
		}

        /// <summary>
        ///		Creates callback delegate .
        /// </summary>
        internal Delegate CreateAction ()
        {
            var target = TargetObject.Target;
            if (target != null) 
			{
                // Rehydrate into a real Action object, 
				// so that the method can be invoked on the target.
                return Delegate.CreateDelegate( 
					this._delegateType, this.TargetObject.Target, this._method );
            }

            return null;
        }
    }


    public class WeakActionEvent<T>
    {
		/// <summary>
		///		Contains all handlers .
		/// </summary>
		readonly List<WeakEventAction> _handlers = new List<WeakEventAction> ();


      
		/// <summary>
		/// 
		/// </summary>
        private void Add ( Action<T> handler )
        {
            var @params = handler.Method.GetParameters();

			if ( @params != null && @params.Length > 1 )
                throw new InvalidOperationException ( "Action should have only 0 or 1 parameter" );

			if ( this._handlers.Any( x => x.TargetObject.Target == handler.Target) )
            {
                return;
            }

			var paramType = ( @params == null || @params.Length == 0 )
									? null
									: @params[0].ParameterType;

            this._handlers.Add ( new WeakEventAction( handler.Target, handler.Method, paramType) );
        }

		/// <summary>
		/// 
		/// </summary>
		private void Remove ( Action<T> handler )
        {
            foreach ( var x in _handlers )
                if ( x.TargetObject.Target == handler.Target )
                {
                    _handlers.Remove(x);
                    return;
                }
        }


		public static WeakActionEvent<T> operator + ( WeakActionEvent<T> e, Action<T> handler )
        {
            e.Add( handler );
            return e;
        }
        
		public static WeakActionEvent<T> operator -(WeakActionEvent<T> e, Action<T> handler)
        {
            e.Remove(handler);
            return e;
        }
       


        internal void Invoke ( T arg )
        {
            for ( var i = this._handlers.Count - 1; i > -1; --i )
            {
                var weakAction = this._handlers[i];

                if ( !weakAction.TargetObject.IsAlive )
				{
					this._handlers.RemoveAt(i);
                }
				else
                {
                    var action = weakAction.CreateAction();
                    action.DynamicInvoke(arg);
                }
            }
        }
    }
}
